/**
Author: Double Sine
Rewrite by: fonaix
License: GPLv3

============
ReadMe:
$ mobaxterm_keygen.exe username version
like this:
$ mobaxterm_keygen.exe admin 21.4
*/
use std::io::Write;
use std::{str, usize};
use zip::write::SimpleFileOptions;
use zip::ZipWriter;

fn variant_base64_encode(bs: &Vec<u32>) -> Vec<u8> {
    let variant_base64_table: Vec<char> =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
            .chars()
            .collect();
    let variant_base64_dict = variant_base64_table
        .iter()
        .enumerate()
        .map(|(i, &c)| (i, c))
        .collect::<std::collections::HashMap<usize, char>>();

    // let variant_base64_reverse_dict = variant_base64_table
    //     .iter()
    //     .enumerate()
    //     .map(|(i, &c)| (c, i))
    //     .collect::<std::collections::HashMap<char, usize>>();

    let mut result: Vec<u8> = Vec::new();
    let blocks_count = bs.len() / 3;
    let left_bytes = bs.len() % 3;
    for i in 0..blocks_count {
        let coding_int = bs[3 * i] | (bs[3 * i + 1] << 8) | (bs[3 * i + 2] << 16);
        result.push(u8::try_from(variant_base64_dict[&((coding_int & 0x3f) as usize)]).unwrap());
        result.push(
            u8::try_from(variant_base64_dict[&(((coding_int >> 6) & 0x3f) as usize)]).unwrap(),
        );
        result.push(
            u8::try_from(variant_base64_dict[&(((coding_int >> 12) & 0x3f) as usize)]).unwrap(),
        );
        result.push(
            u8::try_from(variant_base64_dict[&(((coding_int >> 18) & 0x3f) as usize)]).unwrap(),
        );
    }
    match left_bytes {
        0 => result,
        1 => {
            let coding_int = u32::from_le_bytes([bs[3 * blocks_count] as u8, 0, 0, 0]);
            result
                .push(u8::try_from(variant_base64_dict[&((coding_int & 0x3f) as usize)]).unwrap());
            result.push(
                u8::try_from(variant_base64_dict[&(((coding_int >> 6) & 0x3f) as usize)]).unwrap(),
            );
            result
        }
        _ => {
            let coding_int = u32::from_le_bytes([
                bs[3 * blocks_count] as u8,
                bs[3 * blocks_count + 1] as u8,
                0,
                0,
            ]);
            result
                .push(u8::try_from(variant_base64_dict[&((coding_int & 0x3f) as usize)]).unwrap());
            result.push(
                u8::try_from(variant_base64_dict[&(((coding_int >> 6) & 0x3f) as usize)]).unwrap(),
            );
            result.push(
                u8::try_from(variant_base64_dict[&(((coding_int >> 12) & 0x3f) as usize)]).unwrap(),
            );
            result
        }
    }
}

// fn variant_base64_decode(s: &[u8]) -> Vec<u8> {
//     let mut result = Vec::new();
//     let blocks_count = s.len() / 4;
//     let left_bytes = s.len() % 4;

//     for i in 0..blocks_count {
//         let block = (s[4 * i] as usize)
//             | ((s[4 * i + 1] as usize) << 6)
//             | ((s[4 * i + 2] as usize) << 12)
//             | ((s[4 * i + 3] as usize) << 18);
//         result.push(block as u8);
//         result.push((block >> 8) as u8);
//         result.push((block >> 16) as u8);
//     }

//     match left_bytes {
//         0 => result,
//         1 => {
//             let block = (s[4 * blocks_count] as usize) | ((s[4 * blocks_count + 1] as usize) << 6);
//             result.push(block as u8);
//             result
//         }
//         3 => {
//             let block = (s[4 * blocks_count] as usize)
//                 | ((s[4 * blocks_count + 1] as usize) << 6)
//                 | ((s[4 * blocks_count + 2] as usize) << 12);
//             result.push(block as u8);
//             result.push((block >> 8) as u8);
//             result
//         }
//         _ => Vec::new(),
//     }
// }

fn encrypt_bytes(mut key: u32, bs: Vec<char>) -> Vec<u32> {
    let mut result = Vec::new();
    for b in bs {
        result.push((b as u32) ^ ((key >> 8) & 0xff));
        key = (result.last().unwrap() & key) | 0x482D;
    }
    result
}

// fn decrypt_bytes(mut key: u32, bs: &[char]) -> Vec<u32> {
//     let mut result = Vec::new();
//     for &b in bs {
//         result.push((b as u32) ^ ((key >> 8) & 0xff));
//         key = (b as u32) & key | 0x482D;
//     }
//     result
// }

enum LicenseType {
    Professional = 1,
    // Educational = 3,
    // Personal = 4,
}

fn generate_license(
    license_type: LicenseType,
    count: i32,
    user_name: &str,
    major_version: i32,
    minor_version: i32,
) {
    assert!(count >= 0);
    let license_string = format!(
        "{}#{}|{}{}#{}#{}3{}6{}#{}#{}#{}#",
        license_type as i32,
        user_name,
        major_version,
        minor_version,
        count,
        major_version,
        minor_version,
        minor_version,
        0,
        0,
        0
    );
    let encrypted_license = encrypt_bytes(0x787, license_string.chars().collect());
    let encoded_license = variant_base64_encode(&encrypted_license);
    let path = std::path::Path::new("Custom.mxtpro");
    let file = std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .open(path)
        .unwrap();
    let mut zip_writer = ZipWriter::new(file);
    let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
    zip_writer.start_file("Pro.key", options).unwrap();
    zip_writer.write_all(&encoded_license).unwrap();
    zip_writer.finish().unwrap();
    println!("[*] Success!");
    println!("[*] File generated: {}", path.display());
    println!("[*] Please move or copy the newly-generated file to MobaXterm's installation path.");
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    match args.len() {
        3 => {
            let versions: Vec<i32> = args[2]
                .split('.')
                .map(|arg| arg.parse::<i32>().unwrap())
                .collect();
            generate_license(
                LicenseType::Professional,
                1,
                &args[1],
                versions[0],
                versions[1],
            );
        }
        _ => println!(
            "{}\n{}\n\n{}\n{}\n{}",
            "Usage:",
            "    mobaxterm_keygen <UserName> <Version>",
            "    <UserName>:      The Name licensed to",
            "    <Version>:       The Version of MobaXterm",
            "                     Example:    10.9",
        ),
    }
}
